package com.lecoboy.redisdistributedlock;

import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

@Service
public class RedisLockImpl implements Lock {
    //要锁的key
    private final String redis_key = "KEY";
    //超时时长
    private final int redis_time = 3;
    //订阅通道，当释放锁后进行通知阻塞中的线程
    private final String REDIS_CHANNEL_NAME = "redis_channel_name";
    //线程传值使用
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    //线程池控制
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
    //锁续命使用，定时监控线程情况
    private static ConcurrentHashMap<String, Future> futures = new ConcurrentHashMap<>();

    @Override
    public void lock() {
        //1.尝试加锁
        if (tryLock()) {
            return;
        }
        String uuid = threadLocal.get();
        CountDownLatch cdl = new CountDownLatch(1);//线程协调
        Subscriber subscriber = new Subscriber(cdl, Thread.currentThread());//定义订阅对象
        Jedis jedis = JedisUtil.getJedis();

        new Thread(new Runnable() {
            @Override
            public void run() {
                jedis.subscribe(subscriber, REDIS_CHANNEL_NAME);
            }
        }).start();
        //printLog("开始订阅");
        try {
            cdl.await();//阻塞等待解锁
            //printLog("开始抢夺锁");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //取消订阅
        subscriber.unsubscribe(REDIS_CHANNEL_NAME);
        //重复
        lock();
    }

    private void setHeartBeat(String uuid) {
        //如果有心跳检测则直接返回
        if (futures.contains(uuid)) {
            printLog("已经有心跳了");
            return;
        }
        Thread thread = Thread.currentThread();
        //20s启动一个任务去心跳
        Future future = scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Jedis jedis = JedisUtil.getJedis();
                System.out.println(thread.getName() + "心跳检测");
                if (jedis.get(redis_key).equals(uuid)) {
                    //任务存在，说明任务健康，但快到失效时间，则续命
                    //续命
                    System.out.println(thread.getName() + "锁续命 +3s");
                    jedis.expire(redis_key, redis_time);
                } else {
                    System.out.println(thread.getName() + "锁已释放");

                    //任务节点不存在，咋说明锁已经释放，调度任务取消
                    futures.get(uuid).cancel(true);
                    futures.remove(uuid);
                }
                jedis.close();
            }
        }, 1, redis_time-1, TimeUnit.SECONDS);
        //printLog("启动心跳");
        futures.put(uuid, future);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        threadLocal.set(null);
        //printLog("尝试获取锁");
        String uuid = UUID.randomUUID().toString();
        Jedis jedis = JedisUtil.getJedis();
        String ret = jedis.set(redis_key, uuid, "NX", "PX", redis_time * 1000);
        if ("OK".equals(ret)) {
            threadLocal.set(uuid);
            printLog("获得锁成功");
            //开启心跳
            setHeartBeat(uuid);
            jedis.close();
            //加锁成功
            return true;
        }
        jedis.close();
        return false;
    }

    @Override
    public void unlock() {
        String uuid = threadLocal.get();
        printLog("开始解锁");

        String luaScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                "   return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "   return 0\n" +
                "end";
        Jedis jedis = JedisUtil.getJedis();
        //执行lua脚本  KEYS  ARGV 两个参数
        jedis.eval(luaScript, Collections.singletonList(redis_key), Collections.singletonList(uuid));
        //解锁时停止心跳
        Future future = futures.get(uuid);
        future.cancel(true);
        futures.remove(uuid);
        //解锁后发送广播通知阻塞中的线程
        jedis.publish(REDIS_CHANNEL_NAME, "unlock");
        printLog("发送释放锁广播");
        jedis.close();
    }

    private void printLog(String name) {
        System.out.println(Thread.currentThread().getName() + name + (threadLocal.get() == null ? "" : threadLocal.get()));
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }


}
